Instalar librerias necesarias (en modo silencioso -q):
[1]:
!wget -q https://raw.githubusercontent.com/arqlm/arqlm.github.io/main/_static/libraries.txt
!pip install -q -r /content/libraries.txt
!pip install -q --upgrade plotly[kaleido]
!rm -r /content/libraries.txt
!rm -r /content/sample_data/ ## Esta linea no es necesaria cuando no se trabaja en colab.google
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7.9/7.9 MB 35.8 MB/s eta 0:00:00
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 51.5/51.5 kB 1.7 MB/s eta 0:00:00
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 9.6/9.6 MB 64.7 MB/s eta 0:00:00
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 51.3/51.3 kB 2.9 MB/s eta 0:00:00
[71]:
## Importar librerías
import pandas as pd
import numpy as np
import base64
import datetime
import io
import plotly.io as pio
# pio.renderers.default = "colab"
pio.renderers.default = "notebook" # usar esta linea en jupyter notebook o vscode
import plotly.graph_objects as go
import plotly.express as px
import plotly.offline as py
Carga de archivos
[72]:
import pandas as pd
import numpy as np
# Cargar base de datos en pandas
DH = pd.read_csv('EvYacData.csv', sep=',', encoding='latin1')
DH
[72]:
| Este | Norte | Elevación | au_ppm | ag_ppm | cu_pct | aucn_ppm | cucn_ppm | Zmin | Alte | Lito | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 472186.686 | 6925804.447 | 4220.763 | -99.00 | -99.0 | -99.000 | -99.0 | -99.0 | OXI | ILL_CLO | IBX_MM |
| 1 | 472187.202 | 6925805.493 | 4213.861 | 0.30 | 2.3 | 0.011 | -99.0 | -99.0 | OXI | ILL_CLO | IBX_MM |
| 2 | 472187.343 | 6925805.770 | 4211.986 | 0.47 | 16.2 | 0.032 | -99.0 | -99.0 | OXI | ILL_CLO | IBX_MM |
| 3 | 472187.493 | 6925806.060 | 4210.013 | 0.31 | 2.3 | 0.018 | -99.0 | -99.0 | OXI | ILL_CLO | IBX_MM |
| 4 | 472187.642 | 6925806.351 | 4208.040 | 0.29 | 2.1 | 0.010 | -99.0 | -99.0 | OXI | ILL_CLO | IBX_MM |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 77836 | 471293.198 | 6925823.101 | 3761.674 | 0.03 | 1.0 | 0.002 | -99.0 | -99.0 | BACK | PROP | ECS |
| 77837 | 471293.540 | 6925824.040 | 3759.942 | 0.01 | 0.7 | 0.001 | -99.0 | -99.0 | BACK | PROP | ECS |
| 77838 | 471293.882 | 6925824.980 | 3758.209 | 0.01 | 0.4 | 0.001 | -99.0 | -99.0 | BACK | PROP | ECS |
| 77839 | 471294.224 | 6925825.920 | 3756.477 | 0.06 | 1.3 | 0.004 | -99.0 | -99.0 | BACK | PROP | ECS |
| 77840 | 471294.607 | 6925826.972 | 3754.538 | 0.06 | 0.5 | 0.003 | -99.0 | -99.0 | BACK | PROP | ECS |
77841 rows × 11 columns
2. Distribución estadística de valores
Además de visualizar mapas de los datos, interesa conocer la distribución estadística de sus valores. Para ello, existen varias herramientas, siendo la más conocida el histograma.
2.1. Histograma
El histograma representa gráficamente las frecuencias de ocurrencia en función del valor. Consiste en dividir el rango de los valores en intervalos (generalmente, con el mismo ancho) y visualizar la proporción de datos que caben dentro de cada intervalo (Figura 5). Las clases se miden en el eje de abscisa, mientras que el número de observaciones o las frecuencias se miden en el eje de ordenada.
[73]:
import plotly.express as px
fig = px.histogram(DH, x="cu_pct", nbins=20)
fig.show()
Figura 5: Detecccion de outliers mediante el histograma de las concentraciones de cobre (en porcentaje).
El histograma es una herramienta útil para detectar valores atípicos (“outliers”). Ahora, cabe destacar que un dato atípico no es forzosamente falso y nunca debe ser eliminado sin razón (por ejemplo, un error de transcripción, una falla en el protocolo de medición o un valor ausente codificado como -99). Podría reflejar el comportamiento verdadero de la variable regionalizada y eliminarlo impediría prever la ocurrencia de tales valores en las zonas no muestreadas.
Una vez identificados estos valores ausente, codificados como -99, es posible hacerlos «invisibles» para temas de computo dentro del contexto de Python y que no sean considerados en futuros cómputos de estadísticas. Para esto, es util filtrar los valores negativos y cambiarlos a valores NaN (Not a Number, en ingles):
[74]:
import warnings
warnings.simplefilter(action="ignore")
DH['cu_pct'][DH['cu_pct'] <= 0] = np.nan
Podemos desplegar a traves de pandas las estadísticas básicas de nuestra base de datos en donde podremos verificar que los valores mínimos son ceros para el caso de nuestra variables cobre, y no -99:
[75]:
desc_stats = DH.describe().round(2).T
desc_stats
[75]:
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| Este | 77841.0 | 472167.00 | 417.18 | 470550.67 | 471937.09 | 472177.12 | 472385.55 | 474382.32 |
| Norte | 77841.0 | 6925448.03 | 424.78 | 6921953.90 | 6925295.27 | 6925496.92 | 6925676.57 | 6927699.68 |
| Elevación | 77841.0 | 3978.65 | 257.31 | 2884.13 | 3802.28 | 4030.39 | 4183.49 | 4526.58 |
| au_ppm | 77841.0 | -0.28 | 8.50 | -99.00 | 0.09 | 0.32 | 0.62 | 136.33 |
| ag_ppm | 77841.0 | 1.31 | 26.69 | -99.00 | 0.30 | 0.70 | 1.20 | 4704.00 |
| cu_pct | 76907.0 | 0.14 | 0.20 | 0.00 | 0.02 | 0.08 | 0.21 | 8.30 |
| aucn_ppm | 77841.0 | -87.03 | 32.33 | -99.00 | -99.00 | -99.00 | -99.00 | 16.03 |
| cucn_ppm | 77841.0 | 29.96 | 648.99 | -99.00 | -99.00 | -99.00 | -99.00 | 52810.00 |
Podemos desplegar ahora el histograma, agregando la estadística específica de la variable en análisis
[76]:
# pio.renderers.default = "colab" # usar esta linea si estas en colab
from matplotlib.offsetbox import AnchoredText
# create the bins
counts, bins = np.histogram(DH['cu_pct'], bins=np.arange(0, 1, 0.05),density=True) ## Usar density=True para normalizar el histograma, False para que el histograma sea una cuenta de los datos
bins = 0.5 * (bins[:-1] + bins[1:])
fig = px.bar(x=bins, y=counts, labels={'x': 'cu_pct', 'y': 'Densidad de Probabilidad'}, color_discrete_sequence=['black']) ## Recuerde cambiar el eje Y a 'conteo' si se quiere ver el conteo de los datos
# Add descriptive statistics as annotation (as plain string)
desc_stats_fig = DH['cu_pct'].describe().round(2).to_string()
fig.add_annotation(text=desc_stats_fig.replace('\n', '<br>'),
xref="paper", yref="paper",
x=0.9, y=0.95, showarrow=False,
align='left',
font=dict(family="Times New Roman", size=16, color="black"),
bordercolor="white",
borderwidth=1,
bgcolor="white",
opacity=0.8)
# Update xaxis properties
fig.update_xaxes(gridcolor='rgba(0,0,0,0.1)')
# Update yaxis properties
fig.update_yaxes(gridcolor='rgba(0,0,0,0.1)')
# otras opciones para controlar el color de los elementos de la figura
fig.update_layout(font_family="Times New Roman",font_size=14,
font_color="black", plot_bgcolor='white', autosize = False,
width = 500,
height = 500,)
fig.show()
Figura 6: Histograma de las concentraciones de cobre (en porcentaje).
La visualización del histograma de los datos también es un primer medio de verificar su homogeneidad. Eventualmente, una división del campo en varias sub-zonas será necesaria. Así, por ejemplo, un histograma multimodal puede conducir a la identificación, entre los datos, de varias “poblaciones” susceptibles de estar geográficamente separadas. A veces, tal separación está impuesta por consideraciones físicas, que impiden mezclar todos los datos: presencia de un obstáculo natural (falla, río…), partición de una zona mineralizada según la caracterización mineralógica o el tipo de roca, etc. En tales casos, un problema que puede plantearse es la delimitación de las sub-zonas “homogéneas”, pues es poco frecuente que sus fronteras puedan ser identificadas con exactitud.
Un histograma es, por lo tanto, un gráfico de barras que representa una distribución de frecuencia. La distribución de frecuencia fracciona los datos en grupos o clases e indica el número de observaciones en cada clase (Tabla 1), o el número de observaciones en cada clase dividido por el número total de observaciones.
[77]:
import numpy as np
import plotly.graph_objects as go
# Use bins that include 1 as the upper limit
counts, bins = np.histogram(DH['cu_pct'], bins=np.append(np.arange(0, 1.01, 0.1), np.inf))
# Format class labels like "0 ≤ V < 10"
class_labels = [f"{bins[i]:.1f} ≤ Cu [%] < {bins[i+1]:.1f}" for i in range(len(counts))]
# Replace 'inf' with '∞' in the last label
if class_labels and 'inf' in class_labels[-1]:
class_labels[-1] = class_labels[-1].replace('inf', '∞')
# Calculate percentages
percentages = np.round(100 * counts / counts.sum()).astype(int)
# Create Plotly table
fig = go.Figure(data=[go.Table(
header=dict(
values=["Clase", "Número de observaciones", "Porcentaje"],
fill_color='lightgray',
align='center',
font=dict(size=14, color='black')
),
cells=dict(
values=[class_labels, counts, percentages],
align='center',
font=dict(size=12),
height=30
))
])
fig.update_layout(
title="Tabla de frecuencia, Cu [%]",
title_x=0.5
)
fig.show()
Tabla 1: Tabla de frecuencia para datos de ley de cobre.
2.2. Diagrama de caja
A veces, se acompaña el histograma con un diagrama de caja (box plot) que presenta un solo eje en el cual se representan cinco cuantiles: los cuantiles a 2.5% y 97.5%, el primer y el tercer cuartil y la mediana (Figura 7). Entre los dos cuantiles extremos, se observa el 95% de los datos, mientras que entre el primer cuartil y la mediana se observa el 25% de los datos, al igual que entre la mediana y el tercer cuartil. El diagrama de caja permite resumir algunas características de la distribución, tal como su simetría y su dispersión.
[78]:
from matplotlib.offsetbox import AnchoredText
# Use plotly express histogram with marginal box plot
fig = px.histogram(
DH[(DH['cu_pct'] > 0) & (DH['cu_pct'] <= 1)],
x="cu_pct",
nbins=20,
marginal="box", # This adds a marginal box plot
labels={'cu_pct': 'cu_pct', 'y': 'Densidad de Probabilidad'},
color_discrete_sequence=['black'],
opacity=0.8,
histnorm='probability density'
)
# Update xaxis properties
fig.update_xaxes(gridcolor='rgba(0,0,0,0.1)')
# Update yaxis properties
fig.update_yaxes(gridcolor='rgba(0,0,0,0.1)')
# otras opciones para controlar el color de los elementos de la figura
fig.update_layout(font_family="Times New Roman",font_size=14,
font_color="black", plot_bgcolor='white', autosize = False,
width = 500,
height = 500,)
fig.show()
Figura 7: Diagrama de caja para las concentraciones de cobre.
2.3. Histograma acumulado
En la Tabla 2.2 hemos tomado la información de la Tabla 2.1 y la hemos presentado en forma acumulativa. En lugar de registrar el número de valores dentro de ciertas clases, registramos el número total de valores por debajo de ciertos umbrales. El histograma acumulativo correspondiente, mostrado en la Figura 2.3, es una función no decreciente entre 0 y 100%. Las formas de frecuencia porcentual y frecuencia porcentual acumulativa se usan indistintamente, ya que una puede obtenerse a partir de la otra.
[79]:
import numpy as np
import plotly.graph_objects as go
# Compute histogram (ensure bins and counts are correct)
counts, bin_edges = np.histogram(DH['cu_pct'], bins=np.arange(-0.08, 1., 0.1))
# Compute cumulative counts and percentages
cumulative_counts = np.cumsum(counts)
cumulative_percentages = (100 * cumulative_counts / cumulative_counts[-1])
# Define class labels as "V < x"
class_labels = [f"Cu [%] < {bin_edges[i+1]:.2f}" for i in range(len(counts))]
# Append the final cumulative value for ∞
class_labels.append("Cu [%] < ∞")
cumulative_counts = np.append(cumulative_counts, cumulative_counts[-1])
cumulative_percentages = np.append(cumulative_percentages, 100)
# Create Plotly table
fig = go.Figure(data=[go.Table(
header=dict(
values=["Clase", "Número de observaciones", "Percentage"],
fill_color='lightgray',
align='center',
font=dict(size=14, color='black')
),
cells=dict(
values=[
class_labels,
cumulative_counts,
np.round(cumulative_percentages, 1) # show only one decimal
],
align='center',
font=dict(size=12),
height=30
))
])
fig.update_layout(
title="Tabla de frecuencia acumulada, Cu [%]",
title_x=0.5
)
fig.show()
Tabla 2: Tabla de frecuencia acumulada para datos de cobre.
[80]:
fig = go.Figure(data=[go.Histogram(x=DH['cu_pct'], cumulative_enabled=True, nbinsx=100)]) # Cambia el número de bins aquí
fig.update_layout(
xaxis_title='Cu [%]',
yaxis_title='Frecuencia acumulada',
hovermode=False
)
fig.show()
Figura 8: Histograma acumulado de las concentraciones de cobre (en porcentaje).
2.4. Gráfico de probabilidad
Este gráfico sirve para comparar una distribución experimental con una distribución de referencia (en general, una normal o una lognormal). Consiste en cambiar la escala de los ejes del histograma acumulado de tal modo que, si la distribución experimental coincide con la distribución de referencia, se dibujaría una recta. En el caso de los datos de cobalto, la distribución difiere de una normal (Figuras 8 y 9).
[81]:
import numpy as np
import plotly.graph_objects as go
import scipy.stats as stats
from statsmodels.distributions.empirical_distribution import ECDF
def create_normal_probability_plot(values, title="Normal Probability Plot"):
"""
Create a proper normal probability plot where:
- y-axis shows normal scores (inverse normal CDF of probabilities)
- fitted normal appears as straight line
- matches the style of imagen.png
Parameters:
- values: Array of data values
- title: Plot title
Returns:
- Plotly Figure object
"""
# Sort values
values_sorted = np.sort(values)
n = len(values_sorted)
# Calculate plotting positions (probabilities)
# Using (i-0.5)/n as in your original implementation
cumprob = (np.arange(1, n+1) - 0.5) / n
# Transform probabilities to normal scores
normal_scores = stats.norm.ppf(cumprob)
# Fit normal distribution
loc, scale = stats.norm.fit(values_sorted)
# Create figure
fig = go.Figure()
# Add empirical data points
fig.add_trace(go.Scatter(
showlegend=False,
x=values_sorted,
y=normal_scores,
mode='markers',
marker=dict(color='black', size=5, opacity=1),
name='Data Points'
))
# Custom y-axis to match imagen.png
y_ticks = stats.norm.ppf([0.0001, 0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95, 0.99, 0.99999])
y_ticklabels = ['0.001%','1%', '5%', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '95%', '99%', '99.999%']
fig.update_layout(
title=title,
title_x=0.5,
xaxis_title='Cu [%]',
# xaxis=dict(
# type='log', # This makes x-axis logarithmic
# ),
yaxis_title='Probabilidad',
yaxis=dict(
tickmode='array',
tickvals=y_ticks,
ticktext=y_ticklabels,
range=[stats.norm.ppf(0.00001), stats.norm.ppf(0.99999)]
),
width=800,
height=600,
showlegend=True,
hovermode=False
)
# Update xaxis properties
fig.update_xaxes(gridcolor='rgba(0,0,0,0.1)')
# Update yaxis properties
fig.update_yaxes(gridcolor='rgba(0,0,0,0.1)')
# otras opciones para controlar el color de los elementos de la figura
fig.update_layout(font_family="Times New Roman",font_size=14,
font_color="black", plot_bgcolor='white', autosize = False,
width = 500,
height = 500,)
return fig
# Example usage
if __name__ == '__main__':
# Create and show plot
fig = create_normal_probability_plot(DH['cu_pct'].dropna().values, "Gráfico de probabilidad normal")
fig.show()
Figura 9: Gráfico de probabilidad normal para las concentraciones de cobre.
[82]:
import numpy as np
import plotly.graph_objects as go
import scipy.stats as stats
from statsmodels.distributions.empirical_distribution import ECDF
def create_normal_probability_plot(values, title="Normal Probability Plot"):
"""
Create a proper normal probability plot where:
- y-axis shows normal scores (inverse normal CDF of probabilities)
- fitted normal appears as straight line
- matches the style of imagen.png
Parameters:
- values: Array of data values
- title: Plot title
Returns:
- Plotly Figure object
"""
# Sort values
values_sorted = np.sort(values)
n = len(values_sorted)
# Calculate plotting positions (probabilities)
# Using (i-0.5)/n as in your original implementation
cumprob = (np.arange(1, n+1) - 0.5) / n
# Transform probabilities to normal scores
normal_scores = stats.norm.ppf(cumprob)
# Fit normal distribution
loc, scale = stats.norm.fit(values_sorted)
# Create figure
fig = go.Figure()
# Add empirical data points
fig.add_trace(go.Scatter(
showlegend=False,
x=values_sorted,
y=normal_scores,
mode='markers',
marker=dict(color='black', size=5, opacity=1),
name='Data Points'
))
# Custom y-axis to match imagen.png
y_ticks = stats.norm.ppf([0.0001, 0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95, 0.99, 0.99999])
y_ticklabels = ['0.001%','1%', '5%', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '95%', '99%', '99.999%']
fig.update_layout(
title=title,
title_x=0.5,
xaxis_title='Cu [%]',
xaxis=dict(
type='log', # This makes x-axis logarithmic
),
yaxis_title='Probabilidad',
yaxis=dict(
tickmode='array',
tickvals=y_ticks,
ticktext=y_ticklabels,
range=[stats.norm.ppf(0.00001), stats.norm.ppf(0.99999)]
),
width=800,
height=600,
showlegend=False,
hovermode=False
)
# Update xaxis properties
fig.update_xaxes(gridcolor='rgba(0,0,0,0.1)')
# Update yaxis properties
fig.update_yaxes(gridcolor='rgba(0,0,0,0.1)')
# otras opciones para controlar el color de los elementos de la figura
fig.update_layout(font_family="Times New Roman",font_size=14,
font_color="black", plot_bgcolor='white', autosize = False,
width = 500,
height = 500,)
return fig
# Example usage
if __name__ == '__main__':
# Create and show plot
fig = create_normal_probability_plot(DH['cu_pct'].dropna().values, "Gráfico de probabilidad log-normal")
fig.show()
Figura 10: Gráfico de probabilidad normal para las concentraciones de cobre. Note que la escala del eje de ordenada (frecuencia acumulada) ya no es aritmética.
2.5. Estadísticas elementales
Las características más importantes de de los histogramas pueden capturarse mediante algunas estadísticas resumidas. Las estadísticas resumidas que utilizamos aquí se dividen en tres categorías: medidas de ubicación, medidas de dispersión y medidas de forma.
Las estadísticas del primer grupo nos brindan información sobre dónde se encuentran diversas partes de la distribución. La media, la mediana y la moda pueden darnos una idea de dónde se encuentra el centro de la distribución. La ubicación de otras partes de la distribución se indica mediante diversas cantidades.
El segundo grupo incluye la varianza, la desviación estándar y el rango intercuartil, que se utilizan para describir la variabilidad de los valores de los datos.
La forma de la distribución se describe mediante el coeficiente de asimetría y el coeficiente de variación; el coeficiente de asimetría proporciona información sobre la simetría, mientras que el coeficiente de variación brinda información sobre la longitud de la cola para ciertos tipos de distribuciones. En conjunto, estas estadísticas ofrecen un resumen valioso de la información contenida en el histograma.
Medidas de posición
Media experimental: promedio aritmético de los valores. \begin{equation} m = \frac{1}{N} \sum_{\alpha=1}^{N} z(\mathbf{u}_{\alpha}) \end{equation}
Mediana y Cuartiles: valores que dividen la población en partes de igual número de datos. Por ejemplo, la mediana.
\begin{equation} M =\begin{cases} z(\mathbf{u}_{(N+1)/2}) & \text{si $N$ es impar} \\[3ex] \dfrac{z(\mathbf{u}_{N/2}) + z(\mathbf{u}_{(N/2)+1})}{2} & \text{si $N$ es par} \end{cases} \end{equation}
divide la población en dos partes, mientras que los cuartiles en cuatro partes (\(Q_1\), \(Q_2\) que coincide con la mediana, \(Q_3\), y \(Q_4\) que corresponde al valor máximo) y los quintiles en cinco partes.
Deciles, percentiles y cuantiles: La idea de dividir los datos en mitades con la mediana o en cuartos con los cuartiles se puede extender a cualquier otra fracción. Los deciles dividen los datos en décimas. Una décima parte de los datos se encuentra por debajo del primer decil o del más bajo; dos décimas se encuentran por debajo del segundo decil. El quinto decil corresponde a la mediana. De forma similar, los percentiles dividen los datos en centésimas. El percentil vigésimo quinto es igual al primer cuartil, el percentil quincuagésimo es igual a la mediana y el percentil septuagésimo es igual al tercer cuartil.
Los cuantiles son una generalización de esta idea a cualquier fracción. Por ejemplo, si quisiéramos hablar del valor por debajo del cual cae una vigésima parte de los datos, lo llamamos \(C_{.05}\) en lugar de crear un nuevo nombre en -ile para los vigésimos. Así como ciertos decilos y percentiles son equivalentes a la mediana y los cuartiles, también ciertos cuartiles pueden escribirse como uno de estos estadísticos. Por ejemplo, \(C_{.25}\) es el cuartil inferior, \(C_{.50}\) es la mediana y \(C_{.75}\) es el cuartil superior.
Mínimo y máximo: establecen el rango en el cual se distribuyen los valores.
[83]:
import numpy as np
import plotly.graph_objects as go
import scipy.stats as stats
def create_normal_probability_plot(values, title="Normal Probability Plot"):
"""
Create a normal probability plot with a legend for quartile lines.
"""
# Sort values
values_sorted = np.sort(values)
n = len(values_sorted)
# Calculate plotting positions and normal scores
cumprob = (np.arange(1, n+1) - 0.5) / n
normal_scores = stats.norm.ppf(cumprob)
# Calculate quartiles and median
q1 = np.percentile(values_sorted, 25)
q3 = np.percentile(values_sorted, 75)
median = np.median(values_sorted)
# Create figure
fig = go.Figure()
# Add empirical data points with connecting line
fig.add_trace(go.Scatter(
x=values_sorted,
y=normal_scores,
mode='markers+lines',
marker=dict(color='black', size=5),
line=dict(color='black', width=1),
name='Data',
showlegend=False
))
# Find intersection points with the curve
def find_intersection_y(x_val, x_data, y_data):
idx = np.argmax(x_data >= x_val)
if idx == 0:
return y_data[0]
x0, x1 = x_data[idx-1], x_data[idx]
y0, y1 = y_data[idx-1], y_data[idx]
return y0 + (y1 - y0) * (x_val - x0) / (x1 - x0)
q1_y = find_intersection_y(q1, values_sorted, normal_scores)
median_y = find_intersection_y(median, values_sorted, normal_scores)
q3_y = find_intersection_y(q3, values_sorted, normal_scores)
# Add invisible scatter traces for legend entries
fig.add_trace(go.Scatter(
x=[None], y=[None],
mode='lines',
line=dict(color='red', width=1, dash='dash'),
name=f'Q1: {q1:.2f}',
showlegend=True
))
fig.add_trace(go.Scatter(
x=[None], y=[None],
mode='lines',
line=dict(color='green', width=1, dash='dash'),
name=f'$M$: {median:.2f}',
showlegend=True
))
fig.add_trace(go.Scatter(
x=[None], y=[None],
mode='lines',
line=dict(color='blue', width=1, dash='dash'),
name=f'Q3: {q3:.2f}',
showlegend=True
))
# Add the actual vertical lines (now without labels)
fig.add_shape(type="line",
x0=q1, y0=stats.norm.ppf(0.00001), x1=q1, y1=q1_y,
line=dict(color="red", width=1, dash="dash")
)
fig.add_shape(type="line",
x0=median, y0=stats.norm.ppf(0.00001), x1=median, y1=median_y,
line=dict(color="green", width=1, dash="dash")
)
fig.add_shape(type="line",
x0=q3, y0=stats.norm.ppf(0.00001), x1=q3, y1=q3_y,
line=dict(color="blue", width=1, dash="dash")
)
# Custom y-axis
y_ticks = stats.norm.ppf([0.0001, 0.01, 0.05, 0.1, 0.25, 0.35, 0.45, 0.5, 0.55, 0.65, 0.75, 0.9, 0.95, 0.99, 0.99999])
y_ticklabels = ['0.001%','1%', '5%', '10%', '25%', '35%', '45%', '50%', '55%', '65%', '75%', '90%', '95%', '99%', '99.999%']
fig.update_layout(
title=dict(text=title, x=0.5),
xaxis_title='Cu [%]',
xaxis=dict(
type='log',
gridcolor='rgba(0,0,0,0.1)'
),
yaxis_title='Probabilidad',
yaxis=dict(
tickmode='array',
tickvals=y_ticks,
ticktext=y_ticklabels,
range=[stats.norm.ppf(0.00001), stats.norm.ppf(0.99999)],
gridcolor='rgba(0,0,0,0.1)'
),
legend=dict(
orientation="h",
yanchor="bottom",
y=1.02,
xanchor="right",
x=1
),
font=dict(family="Times New Roman", size=12, color="black"),
plot_bgcolor='white',
width=600,
height=600,
margin=dict(l=60, r=60, b=60, t=60, pad=4),
hovermode=False
)
return fig
# Example usage
if __name__ == '__main__':
# Create and show plot
fig = create_normal_probability_plot(DH['cu_pct'].dropna().values, "Gráfico de probabilidad log-normal")
fig.show()
Figura 11: Gráfico de probabilidad normal para las concentraciones de cobre indicando los quartiles y la mediana de la distribución.
Medidas de dispersión
Varianza experimental: promedio aritmético de la desviación cuadrática entre cada valor y la media. Esta medida cuantifica la dispersión del histograma y se expresa en el cuadrado de la unidad de la variable en estudio.
\begin{equation} \sigma^2 = \frac{1}{N} \sum_{\alpha=1}^{N} \left( z(u_{\alpha}) - m \right)^2 \end{equation}
Desviación estándar: raíz cuadrada de la varianza; se expresa en la misma unidad que la variable en estudio.
\begin{equation} \sigma = \sqrt{\sigma^2} = \sqrt{\frac{1}{N} \sum_{\alpha=1}^{N} \left( z(u_{\alpha}) - m \right)^2} \end{equation}
Rango intercuartil: Otra medida útil de la dispersión de los valores observados es el rango intercuartil, o \(IQR\) (interquartile range), que corresponde a la diferencia entre los cuartiles superior e inferior y se calcula mediante:
\[IQR = Q_3 - Q_1\]A diferencia de la varianza y la desviación estándar, el rango intercuartílico no utiliza la media como centro de la distribución, por lo que a menudo se prefiere cuando algunos valores atípicos influyen fuertemente en la media. El rango intercuartílico de nuestros valores de Cu es de \(35.50 [\%]\).
Medidas de Forma
Coeficiente de variación (para variables positivas): razón entre la desviación estándar y la media; es adimensional.
\begin{equation} CV = \frac{m}{\sigma} \end{equation}
Coeficiente de Asimetría: Una característica del histograma que las estadísticas anteriores no capturan es su simetría. La medida más utilizada para resumir la simetría es el coeficiente de asimetría, que se define como:
\[\text{Coeficiente de asimetría} = \frac{\frac{1}{N} \sum_{i=1}^{N} (z_i - m)^3}{\sigma^3},\]Donde: > - El numerador es la diferencia promedio al cubo entre los valores y su media > - El denominador es el cubo de la desviación estándar (\(\sigma^3\))
Frecuentemente, no se utiliza la magnitud del coeficiente de asimetría sino únicamente su signo para describir la simetría:
Un histograma con asimetría positiva tiene una cola larga de valores altos hacia la derecha, haciendo que la mediana sea menor que la media. En conjuntos de datos geoquímicos, esto es típico para concentraciones de elementos menores.
Un histograma con asimetría negativa muestra una cola larga de valores bajos hacia la izquierda, donde la mediana excede a la media, patrón común en concentraciones de elementos mayores.
Cuando la asimetría es cercana a cero, el histograma es aproximadamente simétrico y la mediana se aproxima a la media.
Para nuestros mas de 70 mil valores de \(Cu\), el coeficiente de asimetría es positivo y muy lejano a cero (8.98), indicando una cola bien amplia.
En el siguiente código agregamos algunas estadísticas mas para completar esta sección.
[84]:
desc_stats['95%'] = DH.quantile(0.95,numeric_only=True)
desc_stats['IQR'] = desc_stats['75%'] - desc_stats['25%']
desc_stats['Skew'] = DH.skew(numeric_only=True).round(2)
desc_stats
[84]:
| count | mean | std | min | 25% | 50% | 75% | max | 95% | IQR | Skew | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Este | 77841.0 | 472167.00 | 417.18 | 470550.67 | 471937.09 | 472177.12 | 472385.55 | 474382.32 | 472710.591 | 448.46 | 0.41 |
| Norte | 77841.0 | 6925448.03 | 424.78 | 6921953.90 | 6925295.27 | 6925496.92 | 6925676.57 | 6927699.68 | 6925896.514 | 381.30 | -2.35 |
| Elevación | 77841.0 | 3978.65 | 257.31 | 2884.13 | 3802.28 | 4030.39 | 4183.49 | 4526.58 | 4312.277 | 381.21 | -0.71 |
| au_ppm | 77841.0 | -0.28 | 8.50 | -99.00 | 0.09 | 0.32 | 0.62 | 136.33 | 1.230 | 0.53 | -11.32 |
| ag_ppm | 77841.0 | 1.31 | 26.69 | -99.00 | 0.30 | 0.70 | 1.20 | 4704.00 | 3.600 | 0.90 | 89.09 |
| cu_pct | 76907.0 | 0.14 | 0.20 | 0.00 | 0.02 | 0.08 | 0.21 | 8.30 | 0.456 | 0.19 | 8.98 |
| aucn_ppm | 77841.0 | -87.03 | 32.33 | -99.00 | -99.00 | -99.00 | -99.00 | 16.03 | 0.270 | 0.00 | 2.33 |
| cucn_ppm | 77841.0 | 29.96 | 648.99 | -99.00 | -99.00 | -99.00 | -99.00 | 52810.00 | 590.000 | 0.00 | 21.42 |